package okl.lab.components
{
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.text.engine.TextLine;
	import flash.utils.Dictionary;
	
	import flashx.textLayout.compose.StandardFlowComposer;
	import flashx.textLayout.compose.TextFlowLine;
	import flashx.textLayout.container.ContainerController;
	import flashx.textLayout.edit.IEditManager;
	import flashx.textLayout.edit.SelectionManager;
	import flashx.textLayout.elements.FlowElement;
	import flashx.textLayout.elements.LinkElement;
	import flashx.textLayout.elements.ParagraphElement;
	import flashx.textLayout.elements.SpanElement;
	import flashx.textLayout.elements.TextFlow;
	import flashx.textLayout.events.FlowElementMouseEvent;
	import flashx.textLayout.formats.TextLayoutFormat;
	
	import mx.binding.utils.ChangeWatcher;
	import mx.collections.ArrayCollection;
	import mx.controls.Alert;
	import mx.effects.Effect;
	import mx.utils.StringUtil;
	
	import okl.lab.components.events.SpellCheckEvent;
	import okl.lab.components.interfaces.ISpellChecker;
	import okl.lab.components.skins.ErrorBorder;
	import okl.lab.components.skins.SpellCheckerSkin;
	
	import spark.components.Group;
	import spark.components.Label;
	import spark.components.List;
	import spark.components.PopUpAnchor;
	import spark.components.RichEditableText;
	import spark.components.TextArea;
	import spark.components.supportClasses.SkinnableComponent;
	import spark.events.IndexChangeEvent;
	import spark.primitives.Graphic;
	import spark.primitives.Rect;
	
	
	/**
	 *  Dispatched when the component has finished its invalidation on <code>check</check> request
	 *
	 *  <p>At this point component displays misspelled words and is ready to show suggestions.</p>
	 *
	 *  @eventType flash.events.Event
	 */
	[Event(name="spellCheckComplete", type="okl.lab.components.events.SpellCheckEvent")]
	
	/**
	 * Component provides spell checker UI with highlighting misspelled words in <code>text</code> and
	 * interface to replace misspelled word with suggestion
	 * 
	 * ISpellChecker implementation 
	 * @see okl.lab.components.interfaces.ISpellChecker
	 * 
	 * @author "oleksandr.kulinchenko@gmail.com"
	 */
	public class SpellChecker extends SkinnableComponent implements ISpellChecker
	{
		
			
		public function SpellChecker()
		{
			super();			
			setStyle("skinClass", SpellCheckerSkin);
		}
		
		//{region CLASS_INTERFACE (ISpellChecker contract)
		private var __textDirty:Boolean;
		private var _text:String;
		/**
		 * Text for checking
		 */
		public function get text():String
		{
			return _text;
		}
		
		public function set text(value:String):void
		{
			if(_text==value)return;
			_text=value;
			__textDirty=true;
			invalidateProperties();
		}
		
		private var _spellingSuggestions:Dictionary;
		/**
		 * Suggestions hashmap of mispelled words. 
		 * <p>Each element in dictionary has mispelled word as key and an array of suggestions as value</p>
		 * <code>wordCheckFunction</code> can be provided as alternative to build this map.
		 */
		public function set spellingSuggestions(value:Dictionary):void
		{
			_spellingSuggestions=value;
		}
		
		private var _wordCheckFunction:Function=defaultWordCheckFunction;
		/**
		 * Accepts Function <code>myWordCheckFunction(word:String):Array</code>
		 * myWordCheckFunction accepts word and returns array of suggestions.
		 * Used to build <code>spellingSuggestions</code>
		 */
		public function set wordCheckFunction(value:Function):void
		{
			_wordCheckFunction=value;
		}
		
		
		/**
		 * Spell checking and highlight misspells
		 */
		private var __checkDirty:Boolean;
		public function check():void
		{
			__checkDirty = true;
			invalidateProperties();
		}
		//}
		
		//{region LIFE_CYCLE
		override protected function commitProperties():void{
			super.commitProperties();
			
			if(__textDirty){
				//just invalidates text
				var textWrap:SpanElement = new SpanElement();
				textWrap.text = text;
				var paragraph:ParagraphElement = tiCheckText.textFlow.getChildAt(0) as ParagraphElement;
				paragraph.replaceChildren(0, paragraph.numChildren, textWrap);
				__textDirty=false;				
			}
			
			var renderers:Array = new Array();
			if(__checkDirty){
				popSuggestions.displayPopUp=false;
				var wordSeparator:String = " ";
				var words:Array = (text==null) ? [] : text.split(wordSeparator);
				paragraph = tiCheckText.textFlow.getChildAt(0) as ParagraphElement;
				paragraph.replaceChildren(0, paragraph.numChildren);
				var correct:Boolean = true;
				for(var i:uint=0; i<words.length; i++){				
					var curWord:String = words[i];							
					var span:SpanElement = new SpanElement();
					span.text=curWord;				
					var flowElement:FlowElement = span;
					if(getSuggestionsFor(curWord)){					
						var link:LinkElement = new LinkElement();
						link.href=curWord;
						link.addChild(span);
						flowElement=link;
						renderers.push(link);
						correct=false;
					}	
					
					var space:SpanElement = new SpanElement();
					space.text=" ";
					paragraph.addChild(flowElement);
					paragraph.addChild(space);	
				}
				
				var completeEvent:SpellCheckEvent = new SpellCheckEvent(SpellCheckEvent.COMPLETE);
				completeEvent.misspelledWordsRenderers = renderers;
				dispatchEvent(completeEvent);
				__checkDirty=false;
			}
			
		}
		//}
		
		//{region SKIN_PARTS
		//TODO: decide what is really part of component
		[SkinPart(required="true")]public var  tiCheckText:RichEditableText;
		[SkinPart(required="true")]public var misspelledBorder:ErrorBorder;		
		[SkinPart(required="true")]public var popSuggestions:PopUpAnchor;		
		[SkinPart(required="true")]public var popSuggestionsContent:Group;
		[SkinPart(required="true")]public var liPopSuggestions:List;
		[SkinPart(required="true")]public var lblSuggestion:Label;
		[SkinPart(required="true")]public var hideSearch:Effect;
		[SkinPart(required="true")]public var showSearch:Effect;
	
		
		/*
		* Skinning section - adding suggestion popup elements events 
		*/
		override protected function partAdded(partName:String, instance:Object):void{
			super.partAdded(partName, instance);
			if(instance==tiCheckText){
				tiCheckText.textFlow.addEventListener(FlowElementMouseEvent.ROLL_OVER, onTextRollOver);
			}else if(instance==popSuggestionsContent){
				popSuggestionsContent.addEventListener(MouseEvent.ROLL_OUT, onTextRollOut);
			}else if(instance==liPopSuggestions){
				liPopSuggestions.addEventListener(IndexChangeEvent.CHANGE, onSuggestionSelected);
			}	
		}

		override protected function partRemoved(partName:String, instance:Object):void{
			super.partRemoved(partName, instance);
			if(instance==tiCheckText){
				tiCheckText.textFlow.removeEventListener(FlowElementMouseEvent.ROLL_OVER, onTextRollOver);
			}else if(instance==popSuggestionsContent){
				popSuggestionsContent.removeEventListener(MouseEvent.ROLL_OUT, onTextRollOut);
			}else if(instance==liPopSuggestions){
				liPopSuggestions.removeEventListener(IndexChangeEvent.CHANGE, onSuggestionSelected);
			}	

		}
		//}region
		
		//{region INTER_LOGIC
		/**
		 * Default value for <code>wordCheckFunction</code>
		 * <b>Please note that <code>spellingSuggestions</code> should be set to use it
		 * @private
		 */
		private function defaultWordCheckFunction(word:String):Array{
			if(_spellingSuggestions==null){
				throw Error("Please provide spellingSuggestions or wordCheckFunction first");
			}
			return _spellingSuggestions[word];
		}
		
		protected function getSuggestionsFor(word:String):Array{
			return _wordCheckFunction.call(this, word);
		}
		
		/**
		 *	Show suggestions list on mispelled word in popup
		 *	@private
		 */
		private var _rolloveredLink:LinkElement;
		protected function onTextRollOver(event:FlowElementMouseEvent):void{
			_rolloveredLink = event.flowElement as LinkElement;
			if(_rolloveredLink==null)return;
			//setup suggestion popup childs
			var wordBounds:Rectangle=_getWordBounds(_rolloveredLink);		
			misspelledBorder.width = wordBounds.width;
			misspelledBorder.height = wordBounds.height;					
			popSuggestions.move(tiCheckText.x + wordBounds.x, tiCheckText.y);	
			//provide suggestions list
			var word:String = _rolloveredLink.href;	//store word for access, not necessary 
			lblSuggestion.text = StringUtil.substitute("Suggestion for {0}:", word);
			liPopSuggestions.dataProvider=new ArrayCollection(getSuggestionsFor(word));
			//popup
			showSearch.play();
		}
		
		protected function onTextRollOut(event:MouseEvent):void{
			hideSearch.play();
		}
		
		/**
		 *	Use tfl enities to get actual selected link bounds
		 *	@private
		 */
		private function _getWordBounds(selectedLink:LinkElement):Rectangle{
			var start:Number = _rolloveredLink.getAbsoluteStart();			
			var end:Number =  start + _rolloveredLink.getFirstLeaf().text.length;				
			//if(_rolloveredLink.getNextSibling())end=_rolloveredLink.getNextSibling().getAbsoluteStart();			
			var flow:TextFlow = tiCheckText.textFlow;
			var composer:StandardFlowComposer = (flow.flowComposer as StandardFlowComposer);
			var tfline:TextFlowLine = composer.getLineAt(0);
			var line:TextLine = tfline.getTextLine();
			var startRect:Rectangle = line.getAtomBounds(start);
			var endRect:Rectangle = line.getAtomBounds(end);	
			var wordBounds:Rectangle = new Rectangle(startRect.x, startRect.y, endRect.right-startRect.x, tiCheckText.height);	
			wordBounds.inflate(2, 0); //inflate horizontal by 1pixel, because of italic font visual 
			return wordBounds;
		}	

		//TODO: provide ext-method to replace any mispelled word
		/**
		 * Replaces misspelled word with selected suggestion
		 */
		protected function onSuggestionSelected(event:IndexChangeEvent):void{
			var item:String = liPopSuggestions.selectedItem as String;		
			var newEl:SpanElement = new SpanElement();
			newEl.text = item;			
			var index:Number = _rolloveredLink.parent.getChildIndex(_rolloveredLink);
			_rolloveredLink.parent.replaceChildren(index, index+1, newEl);
			popSuggestions.displayPopUp=false;
		}
		//}endregin
	}	
	
}